﻿
using EmperialApps.WeatherSpark.Agent;
using EmperialApps.WeatherSpark.Data;
using EmperialApps.WeatherSpark.Internal;
using Microsoft.Phone.Shell;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Media;
using System.Windows.Shapes;
using System.Windows.Threading;


namespace EmperialApps.WeatherSpark {

    /// <summary>Stores the settings for saved locations.</summary>
    public partial class LocationSettings {

        /// <summary>Gets the name of the <see cref="Location"/> property.</summary>
        public const string LocationPropertyName = "Location";


        /// <summary>Initializes a new instance of the <see cref="LocationSettings"/> class.</summary>
        /// <remarks>(Public parameterless constructor required for serialization.)</remarks>
        public LocationSettings( ) { }

        /// <summary>
        /// Initializes a new instance of the <see cref="LocationSettings"/> class with the specified location values.
        /// </summary>
        public LocationSettings( Coordinate location, string search, ForecastSourceId sourceId, string downloadUri ) {
            this._location = location;
            this.Search = search;
            this.SourceId = sourceId;
            this.DownloadUri = downloadUri;

            this.EnableWideTile = true;

            if( downloadUri != null ) {
                this.LastUpdate = DateTimeOffset.Now;

                // Use values from last forecast as defaults.
                int count = Settings.Count;
                if( count > 0 ) {
                    var lastSettings = Settings.GetLocationSettings( count - 1 );
                    this.Units = lastSettings.Units;
                    this.NightExtremes = lastSettings.NightExtremes;
                    this.FollowingReferenceLine = lastSettings.FollowingReferenceLine;
                }
            }
        }


        /// <summary>Gets the location of the forecast.</summary>
        public Coordinate Location {
            get { return this._location; }
        }

        /// <summary>Gets the source of the forecast.</summary>
        internal ForecastSource Source {
            get { return ForecastSource.FromId( this.SourceId ); }
        }


        /// <summary>Finds the location settings for the specified tile ID.</summary>
        public static LocationSettings TryFind( int tileId ) {
            return Settings.GetAllLocationSettings( ).FirstOrDefault( ls => ls.TileId == tileId );
        }


        /// <summary>Gets the location's save index.</summary>
        public int GetIndex( ) {
            return Settings.IndexOfLocation( this.Location );
        }

        /// <summary>Gets the location's bundled place information.</summary>
        public Place GetPlace( Coordinate? location = null, string link = null ) {
            return new Place( location ?? this.Location, this.GetDisplayName( ), null, link ?? this.DownloadUri );
        }

        /// <summary>Gets the location's display name.</summary>
        public string GetDisplayName( ) {
            return this.GetTileTitle( ForecastDisplayMode.Day );
        }

        /// <summary>Gets the title for the location's tile.</summary>
        public string GetTileTitle( ForecastDisplayMode? displayMode = null ) {
            ForecastDisplayMode tileDisplayMode = displayMode ?? this.Tile;
            string title =
                tileDisplayMode == ForecastDisplayMode.None
                    ? AssemblyInfo.Title
                    : this.TileTitle ?? this.Search;

            return title;
        }

        /// <summary>Gets the effective <see cref="DetailPage"/> level.</summary>
        public ForecastDisplayLevel GetDetailPageLevel( ) {
            ForecastDisplayLevel supportedDisplayLevel = this.Source.SupportedDisplayLevel;
            ForecastDisplayLevel detailPageLevel = this.DetailPageLevel & supportedDisplayLevel;
            if( (detailPageLevel & ForecastDisplayLevel.Set) == 0 )
                detailPageLevel = ForecastDisplayLevel.DetailsMask & supportedDisplayLevel;

            return detailPageLevel;
        }

        /// <summary>Gets the effective <see cref="SummaryPage"/> level.</summary>
        public ForecastDisplayLevel GetSummaryPageLevel( ) {
            ForecastDisplayLevel supportedDisplayLevel = this.Source.SupportedDisplayLevel;
            ForecastDisplayLevel summaryPageLevel = this.SummaryPageLevel & supportedDisplayLevel;
            if( (summaryPageLevel & ForecastDisplayLevel.Set) == 0 )
                summaryPageLevel = ForecastDisplayLevel.SummaryMask & supportedDisplayLevel & ~(ForecastDisplayLevel.HumidityMask | ForecastDisplayLevel.PressureMask);

            return summaryPageLevel;
        }

        /// <summary>Gets the information for the location's tile.</summary>
        public ForecastInfo GetForecastInfo( bool minimal = false ) {
            return this.GetForecastInfoCore( this.Tile, !this.DisableTileFlip, minimal );
        }

        /// <summary>Determines whether the specified property is used by <see cref="GetForecastInfo"/>.</summary>
        public static bool IsForecastInfoProperty( string propertyName ) {
            switch( propertyName ) {
                case UnitsPropertyName:
                case TilePropertyName:
                case TileIdPropertyName:
                case TileTitlePropertyName:
                case DownloadUriPropertyName:
                case LastUpdatePropertyName:
                    return true;

                default:
                    return false;
            }
        }


        /// <summary>Removes the location's secondary tile.</summary>
        public void RemoveTile( ) {
            int index = Settings.IndexOfLocation( this.Location );
            int tileId = this.TileId;
            RemoveTile( tileId, index );
        }

        /// <summary>Removes the secondary tile for a location.</summary>
        public static void RemoveTile( int tileId, int? index = null ) {
            var tile = FindTile( tileId );
            if( tile != null ) {
                string target = index.HasValue ? "for forecast " + index : "ID " + tileId;
                typeof( LocationSettings ).Log( "Removing secondary tile " + target );
                tile.Delete( );
            }
        }

        /// <summary>Updates the location's secondary tile.</summary>
        public void UpdateTile( Shape tileProgress, TileUpdateReason reason ) {
            this.TileProgress = tileProgress;

            int index = Settings.IndexOfLocation( this.Location );
            int tileId = this.TileId;
            var tile = FindTile( tileId );

            // If not displaying a live tile, remove secondary tile.
            if( this.Tile == ForecastDisplayMode.None ) {
                if( tile != null ) {
                    typeof( LocationSettings ).Log( "Deleting secondary tile for forecast " + index );
                    tile.Delete( );
                }
            }
            // If secondary tile has been removed by the user, reset state to None.
            else if( tile == null && reason != TileUpdateReason.CreateTile ) {
                typeof( LocationSettings ).Log( "Resetting secondary tile for forecast " + index + " to None" );
                this.Tile = ForecastDisplayMode.None;
            }
            // Otherwise, start normal schedule on secondary tile (if re-scheduling has not been disabled).
            else if( reason == TileUpdateReason.CreateTile || !Settings.DisableTileRescheduling ) {
                var tileData = this.InitializeTileImageData( forceImageUpdate: reason != TileUpdateReason.Refresh );

                // If secondary tile has not been added yet, create it.
                if( tile == null ) {
                    typeof( LocationSettings ).Log( "Creating secondary tile for forecast " + index );
                    this.EnsureTileId( ref tileId );
                    ForecastInfo info = GetForecastInfo( minimal: true );
                    Uri navigationUri = new Uri( string.Format(
                        "{0}?{1}={2}&{3}={4}",
                        Settings.DetailPage.OriginalString,
                        TileIdPropertyName, tileId,
                        RecoveryDataName, info.Encode( )
                    ), UriKind.Relative );
                    try {
                        ShellTile.Create( navigationUri, tileData, supportsWideTile: this.EnableWideTile );
                    }
                    catch( InvalidOperationException ex ) {
                        if( ex.Message == "Operation is not valid due to the current state of the object." )
                            typeof( LocationSettings ).Log( "- Could not update secondary tile for forecast " + index );
                        else
                            throw;
                    }
                }
                // Otherwise, update current tile image.
                else {
                    typeof( LocationSettings ).Log( "Rescheduling secondary tile for forecast " + index );
                    tile.Update( tileData );
                }
            }
            else {
                typeof( LocationSettings ).Log( "- Skipping tile update for forecast " + index );
            }

            DeleteUnusedTileImages( this.Location, this.Units, this.Tile );
        }

        /// <summary>Attempts to recover basic location settings from the specified tile navigation URI.</summary>
        public static LocationSettings FromTile( string tileUri ) {
            ForecastInfo recoveryInfo;
            if( !GetRecoveryInfo( tileUri, out recoveryInfo ) )
                return null;

            try {
                int tileId;
                string identifier = ForecastInfo.GetIdentifierFromTileUri( tileUri );
                ForecastInfo.TryGetTileId( identifier, out tileId );

                var locationSettings = new LocationSettings( recoveryInfo.Location, recoveryInfo.Title, recoveryInfo.SourceId, null );
                locationSettings.RecoverSettings( recoveryInfo, tileId );
                return locationSettings;
            }
            catch( Exception ex ) {
                typeof( LocationSettings ).Log( "Error recovering forecast from " + tileUri + ": " + Environment.NewLine + ex );
                return null;
            }
        }

        /// <summary>Loads basic settings from the specified forecast info.</summary>
        public void RecoverSettings( ForecastInfo info, int tileId ) {
            Units units;
            ForecastDisplayMode mode;
            bool displayBack, displayWide;
            ConvertValue.FromTileMode( info.Mode, out units, out mode, out displayBack, out displayWide );

            this.TileId = tileId;
            this.SourceId = info.SourceId;
            this.DownloadUri = info.DownloadUri;

            this.Tile = mode;
            this.Units = units;
            this.DisableTileFlip = !displayBack;
            this.EnableWideTile = displayWide;
        }

        /// <summary>Determines whether the recovery settings associated with the tile reflection accurately reflect the current location settings.</summary>
        public bool HasValidRecoverySettings( ) {
            // If forecast does not have a tile, there is nothing to validate.
            int tileId = this.TileId;
            var tile = FindTile( TileId );
            if( tile == null )
                return true;

            // If we could not recover information from the tile, it is invaild.
            ForecastInfo recoveryInfo;
            string tileUri = tile.NavigationUri.ToString( );
            if( !GetRecoveryInfo( tileUri, out recoveryInfo ) )
                return false;


            // Otherwise, compare the essential settings with the recovered information.
            Units units;
            ForecastDisplayMode mode;
            bool displayBack, displayWide;
            ConvertValue.FromTileMode( recoveryInfo.Mode, out units, out mode, out displayBack, out displayWide );
            ForecastInfo info = this.GetForecastInfoCore( mode, displayBack, minimal: true );

            bool valid = recoveryInfo == info;
            return valid;
        }


        /// <inheritdoc/>
        public override string ToString( ) {
            var place = new Place( this.Location, this.Search );
            return string.Format(
                "{0} {1} — {2}, Night {3}, {4} Tile \"{5}\" [{6:o} {7}]",
                this.SourceId, place, this.Units, this.NightExtremes ? "Extremes" : "Flat",
                this.Tile, this.TileTitle, this.LastUpdate, this.DownloadUri );
        }


        #region Private Members

        private const string RecoveryDataName = "RecoveryData";

        private Coordinate _location;

        internal static void InitializeLocation( LocationSettings locationSettings, Coordinate location ) {
            if( locationSettings == null || locationSettings._location == location )
                return;

            Coordinate oldLocation = locationSettings._location;
            locationSettings._location = location;
            if( oldLocation != default( Coordinate ) ) {
                locationSettings.OnPropertyChanged( LocationPropertyName );

                // Delete all images for previous location.
                DeleteUnusedTileImages( oldLocation, (Units)(-1), ForecastDisplayMode.None );
            }
        }

        private static ShellTile FindTile( int tileId ) {
            if( tileId == 0 )
                return null;

            string query = ForecastInfo.CreateIdentifier( tileId );
            ShellTile tile = ShellTile.ActiveTiles.FirstOrDefault( t => t.NavigationUri.ToString( ).Contains( query ) );
            return tile;
        }

        private void EnsureTileId( ref int tileId ) {
            if( tileId != 0 )
                return;

            var existingIds = Settings.GetAllLocationSettings( ).Select( ls => ls.TileId );
            tileId = Enumerable.Range( 1, 9 ).Except( existingIds ).First( );
            this.TileId = tileId;
        }

        private static void DeleteUnusedTileImages( Coordinate location, Units usedUnits, ForecastDisplayMode usedMode ) {
            foreach( Units units in new[] { Units.Metric, Units.Imperial } )
                foreach( ForecastDisplayMode mode in new[] { ForecastDisplayMode.Day, ForecastDisplayMode.Hour } )
                    if( units != usedUnits || mode != usedMode )
                        foreach( string modifier in PathModifiers ) {
                            Uri imageSource = SharedStorage.GetTileImageSource( location, units, mode, modifier );
                            SharedStorage.DeleteTileImage( imageSource );
                        }
        }

        private ForecastInfo GetForecastInfoCore( ForecastDisplayMode displayMode, bool enableTileFlip, bool minimal ) {
            ForecastTileMode mode = ConvertValue.ToTileMode( this.Units, displayMode, enableTileFlip, this.EnableWideTile );
            if( minimal )
                return new ForecastInfo( this.Location, null, mode, this.GetTileTitle( ForecastDisplayMode.Day ), this.DownloadUri, default( TimeSpan ), default( DateTimeOffset ), this.SourceId, default( uint ) );

            string identifier = ForecastInfo.CreateIdentifier( this.TileId );
            string title = this.Tile == ForecastDisplayMode.None ? null : this.GetTileTitle( );
            TimeSpan refreshRate = TimeSpan.FromMinutes( Settings.UpdateLimit.TotalMinutes * 2 );
            return new ForecastInfo( this.Location, identifier, mode, title, this.DownloadUri, refreshRate, this.LastUpdate, this.SourceId, Settings.TileBackgroundColorValue );
        }

        private static bool GetRecoveryInfo( string tileUri, out ForecastInfo recoveryInfo ) {
            int recoveryDataIndex = tileUri.IndexOf( RecoveryDataName );
            if( recoveryDataIndex < 0 ) {
                recoveryInfo = default( ForecastInfo );
                return false;
            }

            try {
                string recoveryData = tileUri.Substring( recoveryDataIndex + RecoveryDataName.Length + 1 );
                recoveryInfo = ForecastInfo.Decode( recoveryData );
                return true;
            }
            catch( Exception ex ) {
                typeof( LocationSettings ).Log( "Error recovering forecast info from " + tileUri + ": " + Environment.NewLine + ex );
                recoveryInfo = default( ForecastInfo );
                return false;
            }
        }

        #region Tile Image Creation

        private static readonly Uri EmptyUri = new Uri( "", UriKind.Relative );
        private static readonly string[] PathModifiers = new[] {
            null,
            SharedStorage.SmallTileImagePathModifier,
            SharedStorage.FlipTileImagePathModifier,
            SharedStorage.WideTileImagePathModifier,
            SharedStorage.WideFlipTileImagePathModifier
        };

        private Action _waitForIdle;
        private TileUpdate _pendingUpdates;
        private Pair<int, DateTime> _idleState;
        private DispatcherTimer _waitForIdleDelay;
        private DateTime _lastTileImageUpdate;

        private FlipTileData InitializeTileImageData( bool forceImageUpdate ) {
            Exception error;
            Forecast forecast;
            Units units = this.Units;
            Coordinate location = this.Location;
            ForecastDisplayMode mode = this.Tile;
            TileUpdate updates = mode == ForecastDisplayMode.None ? TileUpdate.None : TileUpdate.All;

            // If tile image already exists, update at idle.
            FlipTileData tileData;
            Uri imageSource = SharedStorage.GetTileImageSource( location, units, mode );
            if( SharedStorage.TestImageSourcePath( imageSource ) ) {
                this.CreateTileImageOnIdle( updates, forceImageUpdate );
                tileData = new FlipTileData {
                    BackgroundImage = imageSource,
                    BackBackgroundImage = this.GetTileImageSource( location, units, mode, flip: true, wide: false ),
                    WideBackgroundImage = this.GetTileImageSource( location, units, mode, flip: false, wide: true ),
                    WideBackBackgroundImage = this.GetTileImageSource( location, units, mode, flip: true, wide: true ),
                    SmallBackgroundImage = this.GetTileImageSource( location, units, mode, flip: false, wide: false )
                };
            }
            // Otherwise, create standard images immediately, and update others in the background.
            else if( location.TryLoadForecast( typeof( LocationSettings ), this.GetIndex( ), out forecast, out error ) ) {
                tileData = new FlipTileData {
                    BackgroundImage = this.CreateTileImage( forecast, units, mode, ref updates ),
                    BackBackgroundImage = this.CreateTileImage( forecast, units, mode, ref updates ),
                    WideBackgroundImage = this.GetTileImageSource( location, units, mode, flip: false, wide: true ),
                    WideBackBackgroundImage = this.GetTileImageSource( location, units, mode, flip: true, wide: true ),
                    SmallBackgroundImage = this.GetTileImageSource( location, units, mode, flip: false, wide: false )
                };
                this.CreateTileImageOnIdle( updates, forceImageUpdate );
            }
            else {
                imageSource = SharedStorage.GetTileImageSource( location, units, ForecastDisplayMode.None );
                tileData = new FlipTileData {
                    BackgroundImage = imageSource,
                    WideBackgroundImage = EmptyUri,
                    SmallBackgroundImage = imageSource
                };
            }

            tileData.Title = this.GetTileTitle( mode );
            tileData.BackTitle = this.DisableTileFlip ? "" : tileData.Title;

            return tileData;
        }

        private Uri GetTileImageSource( Coordinate location, Units units, ForecastDisplayMode mode, bool flip, bool wide ) {
            if( flip && this.DisableTileFlip )
                return EmptyUri;

            if( wide && !this.EnableWideTile )
                return EmptyUri;


            string modifier = null;
            if( wide )
                modifier += SharedStorage.WideTileImagePathModifier;

            if( flip )
                modifier += SharedStorage.FlipTileImagePathModifier;

            if( modifier == null )
                modifier = SharedStorage.SmallTileImagePathModifier;

            return SharedStorage.GetTileImageSource( location, units, mode, modifier );
        }

        private void CreateTileImageOnIdle( TileUpdate update, bool force ) {
            if( update == TileUpdate.None ) {
                typeof( LocationSettings ).Log( "Ignoring empty tile update request." );
                return;
            }

            TimeSpan lastUpdate = (DateTime.UtcNow - this._lastTileImageUpdate);
            if( lastUpdate.TotalMinutes < 5 ) {
                if( force ) {
                    typeof( LocationSettings ).Log( "Processing frequent tile update request." );
                }
                else {
                    typeof( LocationSettings ).Log( "Ignoring frequent tile update request." );
                    return;
                }
            }

            TileUpdate pending = this._pendingUpdates;
            this._pendingUpdates |= update;
            if( pending != TileUpdate.None ) {
                typeof( LocationSettings ).Log( "Refreshed existing tile update request." );
                return;
            }

            this._idleState = default( Pair<int, DateTime> );
            if( this._waitForIdleDelay == null ) {
                this._waitForIdle = this.WaitForIdle;
                this._waitForIdleDelay = new DispatcherTimer { Interval = TimeSpan.FromSeconds( 1 ) };
                this._waitForIdleDelay.Tick += delegate { this._waitForIdle( ); };
            }

            this._waitForIdleDelay.Start( );
        }

        private void WaitForIdle( ) {
            this._waitForIdleDelay.Stop( );
            if( this.Tile == ForecastDisplayMode.None )
                this._pendingUpdates = TileUpdate.None;

            if( this._pendingUpdates == TileUpdate.None ) {
                this.UpdateTileProgress( TileUpdate.None );
                this._lastTileImageUpdate = DateTime.UtcNow;
                return;
            }

            int dispatchCount = this._idleState.Item1;
            DateTime currentDispatchTime = DateTime.UtcNow;
            DateTime previousDispatchTime = this._idleState.Item2;
            TimeSpan delta = currentDispatchTime - previousDispatchTime;

            bool force = dispatchCount > 100;
            if( force || delta.TotalMilliseconds < 40 ) {
                typeof( LocationSettings ).Log(
                    force ? "Forcing tile update after idle wait expired."
                       : "Performing tile update after idle detected." );

                this.CreateTileImage( this.Location, this.Units, this.Tile, ref this._pendingUpdates );
                currentDispatchTime = default( DateTime );
                dispatchCount = 0;
            }

            this._idleState = Pair.Create( 1 + dispatchCount, currentDispatchTime );
            Deployment.Current.Dispatcher.BeginInvoke( this._waitForIdle );
        }

        private void CreateTileImage( Coordinate location, Units units, ForecastDisplayMode mode, ref TileUpdate updates ) {
            Exception error;
            Forecast forecast;
            if( location.TryLoadForecast( typeof( LocationSettings ), this.GetIndex( ), out forecast, out error ) )
                this.CreateTileImage( forecast, units, mode, ref updates );
        }

        private Uri CreateTileImage( Forecast forecast, Units units, ForecastDisplayMode mode, ref TileUpdate updates ) {
            TileUpdate update = TileUpdate.Last;
            while( !updates.HasFlag( update ) )
                update = (TileUpdate)((int)update / 2);

            this.UpdateTileProgress( update );
            if( updates == TileUpdate.None )
                return EmptyUri;


            updates &= ~update;


            bool isFlipUpdate, isWideUpdate;
            string pathModifier, title;
            switch( update ) {
                case TileUpdate.SmallImage:
                    isFlipUpdate = isWideUpdate = false;
                    pathModifier = SharedStorage.SmallTileImagePathModifier;
                    title = this.GetTileTitle( mode );
                    break;

                case TileUpdate.FlipImage:
                    isFlipUpdate = true;
                    isWideUpdate = false;
                    pathModifier = SharedStorage.FlipTileImagePathModifier;
                    title = null;
                    break;

                case TileUpdate.WideImage:
                    isFlipUpdate = false;
                    isWideUpdate = true;
                    pathModifier = SharedStorage.WideTileImagePathModifier;
                    title = null;
                    break;

                case TileUpdate.WideFlipImage:
                    isFlipUpdate = isWideUpdate = true;
                    pathModifier = SharedStorage.WideFlipTileImagePathModifier;
                    title = null;
                    break;

                default:
                case TileUpdate.SquareImage:
                    isFlipUpdate = isWideUpdate = false;
                    pathModifier = null;
                    title = null;
                    break;
            }


            Uri imageSource;

            if( isFlipUpdate && this.DisableTileFlip ) {
                typeof( LocationSettings ).Log( string.Format( "Ignoring {0} tile image with flip disabled.", GetUpdateName( update ) ) );
                SharedStorage.DeleteTileImage( SharedStorage.GetTileImageSource( forecast.Location, units, mode, pathModifier ) );
                imageSource = EmptyUri;
            }
            else if( isWideUpdate && !this.EnableWideTile ) {
                typeof( LocationSettings ).Log( string.Format( "Ignoring {0} tile image with wide tile disabled.", GetUpdateName( update ) ) );
                SharedStorage.DeleteTileImage( SharedStorage.GetTileImageSource( forecast.Location, units, mode, pathModifier ) );
                imageSource = EmptyUri;
            }
            else {
                typeof( LocationSettings ).Log( string.Format( "Updating {0} {1} {2} tile image.", units, mode, GetUpdateName( update ) ) );
                TileImageBase tileImage = SharedStorage.CreateTileImage( pathModifier, title, units, mode, forecast, Settings.TileBackgroundColorValue );
                imageSource = SharedStorage.SaveTileImage( tileImage );
            }

            return imageSource;
        }


        private static readonly Point[] _tileProgressValues = new[] { new Point( 1, 0 ), new Point( 0, 0 ), new Point( 0, 1 ), new Point( 1, 1 ), new Point( 1, 0 ) };

        private Shape _tileProgress;

        private Shape TileProgress {
            get { return this._tileProgress ?? (this._tileProgress = new Rectangle( )); }
            set {
                if( value == null || value == this._tileProgress )
                    return;

                this._tileProgress = value;

                var fill = (SolidColorBrush)this._tileProgress.Fill;
                uint fillValue = fill == null ? 0 : Display.FromColor( fill.Color );

                uint backgroundValue = Settings.TileBackgroundColorValue;
                if( backgroundValue == 0 ) {
                    var accentBrush = Settings.GetAccentBrush( );
                    backgroundValue = Display.FromColor( accentBrush.Color );
                }

                if( fillValue != backgroundValue )
                    this._tileProgress.Fill = new SolidColorBrush( Display.ToColor( backgroundValue ) );
            }
        }

        private void SetTileProgressFigure( PathFigure figure ) {
            var clip = this.TileProgress.Clip as PathGeometry;
            if( clip == null )
                this.TileProgress.Clip = clip = new PathGeometry( );

            if( figure == null )
                clip.Figures.Clear( );
            else if( clip.Figures.Count == 1 )
                clip.Figures[0] = figure;
            else
                clip.Figures.Add( figure );
        }

        private void UpdateTileProgress( TileUpdate update ) {
            double width = this.TileProgress.Width;
            double height = this.TileProgress.Height;

            PathFigure figure;
            switch( update ) {
                default:
                case TileUpdate.None:
                    figure = null;
                    break;

                case TileUpdate.SmallImage:
                case TileUpdate.WideFlipImage:
                case TileUpdate.WideImage:
                case TileUpdate.FlipImage:
                case TileUpdate.SquareImage:
                    int value = GetUpdateValue( update );
                    var segment = new PolyLineSegment( );
                    foreach( Point progress in _tileProgressValues.Take( value ) )
                        segment.Points.Add( new Point(
                            progress.X * width,
                            progress.Y * height ) );

                    var start = new Point( width / 2, height / 2 );
                    figure = new PathFigure { StartPoint = start, Segments = { segment } };
                    break;
            }

            this.SetTileProgressFigure( figure );
        }


        private static int GetUpdateValue( TileUpdate update ) {
            int value = 1;
            int updateFactor = (int)update;
            while( (updateFactor >>= 1) != 0 )
                ++value;

            return value;
        }

        private static string GetUpdateName( TileUpdate update ) {
            string name;
            switch( update ) {
                case TileUpdate.SmallImage:
                    name = "Small";
                    break;

                case TileUpdate.FlipImage:
                    name = "Flip";
                    break;

                case TileUpdate.WideImage:
                    name = "Wide";
                    break;

                case TileUpdate.WideFlipImage:
                    name = "Wide Flip";
                    break;

                case TileUpdate.SquareImage:
                    name = "Square";
                    break;

                default:
                    name = "—";
                    break;
            }

            return name;
        }

        [Flags]
        private enum TileUpdate {
            None = 0,
            SmallImage = 1 << 0,
            WideFlipImage = 1 << 1,
            WideImage = 1 << 2,
            FlipImage = 1 << 3,
            SquareImage = 1 << 4,

            Last = SquareImage,
            All = SmallImage | WideFlipImage | WideImage | FlipImage | SquareImage
        }

        #endregion

        #endregion

    }

}
